#include <Library/RockchipPlatformLib.h>
#include "Soc.h"
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/IoLib.h>
#include <Library/PcdLib.h>
#include <Library/SpiLib.h>
#include <Library/RK806.h>
#if 0
/* Not used or exisit register and configure */
#define NA  -1
#define BIT(n)  (1 << (n))
#define RK806_DBG  DEBUG_ERROR
/* rk806 buck*/
#define RK806_BUCK_ON_VSEL(n)   (0x1a + n - 1)
#define RK806_BUCK_SLP_VSEL(n)  (0x24 + n - 1)
#define RK806_BUCK_CONFIG(n)    (0x10 + n - 1)
#define RK806_BUCK_VSEL_MASK  0xff

/* RK806 LDO */
#define RK806_NLDO_ON_VSEL(n)   (0x43 + n - 1)
#define RK806_NLDO_SLP_VSEL(n)  (0x48 + n - 1)
#define RK806_NLDO_VSEL_MASK  0xff
#define RK806_PLDO_ON_VSEL(n)   (0x4e + n - 1)
#define RK806_PLDO_SLP_VSEL(n)  (0x54 + n - 1)
#define RK806_PLDO_VSEL_MASK  0xff

/* RK806 ENABLE */
#define RK806_POWER_EN(n)  (0x00 + n)
#define RK806_NLDO_EN(n)   (0x03 + n)
#define RK806_PLDO_EN(n)   (0x04 + n)
#define RK806_RAMP_RATE_MASK1  0xc0
#define RK806_RAMP_RATE_REG1(n)  (0x10 + n)
#define RK806_RAMP_RATE_REG1_8   0xeb
#define RK806_RAMP_RATE_REG9_10  0xea

#define RK806_RAMP_RATE_4LSB_PER_1CLK  0x00 /* LDO 100mV/uS buck 50mV/us */
#define RK806_RAMP_RATE_2LSB_PER_1CLK  0x01 /* LDO 50mV/uS buck 25mV/us */
#define RK806_RAMP_RATE_1LSB_PER_1CLK  0x02 /* LDO 25mV/uS buck 12.5mV/us */
#define RK806_RAMP_RATE_1LSB_PER_2CLK  0x03 /* LDO 12.5mV/uS buck 6.25mV/us */

#define RK806_RAMP_RATE_1LSB_PER_4CLK   0x04 /* LDO 6.28/2mV/uS buck 3.125mV/us */
#define RK806_RAMP_RATE_1LSB_PER_8CLK   0x05 /* LDO 3.12mV/uS buck 1.56mV/us */
#define RK806_RAMP_RATE_1LSB_PER_13CLK  0x06 /* LDO 1.9mV/uS buck 961mV/us */
#define RK806_RAMP_RATE_1LSB_PER_32CLK  0x07 /* LDO 0.78mV/uS buck 0.39mV/us */

#define RK806_PLDO0_2_MSK(pldo)  (BIT(pldo + 5))
#define RK806_PLDO0_2_SET(pldo)  (BIT(pldo + 1) | RK806_PLDO0_2_MSK(pldo))
#define RK806_PLDO0_2_CLR(pldo)  RK806_PLDO0_2_MSK(pldo)

#define RK806_CHIP_NAME  0x5A
#define RK806_CHIP_VER   0x5B

#define RK806_CMD_READ     0
#define RK806_CMD_WRITE    BIT(7)
#define RK806_CMD_CRC_EN   BIT(6)
#define RK806_CMD_CRC_DIS  0
#define RK806_CMD_LEN_MSK  0x0f
#define RK806_REG_H        0x00

#define RK806_SYS_CFG1            0x5f
#define RK806_SYS_CFG3            0x72
#define RK806_PWRON_KEY           0x76
#define RK806_INT_STS0            0x77
#define RK806_INT_MSK0            0x78
#define RK806_INT_STS1            0x79
#define RK806_INT_MSK1            0x7A
#define RK806_GPIO_INT_CONFIG     0x7B
#define RK806_IRQ_PWRON_FALL_MSK  BIT(0)
#define RK806_IRQ_PWRON_RISE_MSK  BIT(1)
#define RK806_DEV_OFF             BIT(0)
#define RK806_RST_MODE1           0x01
#define RK806_RST_MODE2           0x02
#define VERSION_AB                0x01

struct regulator_init_data {
  const char    *supply_regulator;           /* or NULL for system supply */
  INT32         reg_id;
  INT32         init_voltage_mv;
};

struct rk8xx_reg_info {
  UINT32    min_uv;
  UINT32    step_uv;
  UINT8     vsel_reg;
  UINT8     vsel_sleep_reg;
  UINT8     config_reg;
  UINT8     vsel_mask;
  UINT8     min_sel;
  /* only for buck now */
  UINT8     max_sel;
  UINT8     range_num;
};

#define RK8XX_DESC_COM(_name, _reg_info, _ops) \
{                                               \
        .reg_info		= (_reg_info),  \
        .name	= (_reg_id),                    \
        .ops		= _ops,                 \
}

#define RK8XX_VOLTAGE_INIT(_id, _voltage) \
{               \
        .reg_id = (_id),\
        .init_voltage_mv	= (_voltage),\
}

/******************************************
8 -12: MASTER, SLAVE
4 -7: BUCK, NLDO, PLDO
0 -3:  num
******************************************/
#define MASTER  (0x0 << 8)
#define SLAVER  (0x1 << 8)
#define BUCK    (0x0 << 4)
#define NLDO    (0x1 << 4)
#define PLDO    (0x2 << 4)

enum master_num {
  MASTER_BUCK1  = (MASTER | BUCK | 0),
  MASTER_BUCK2  = (MASTER | BUCK | 1),
  MASTER_BUCK3  = (MASTER | BUCK | 2),
  MASTER_BUCK4  = (MASTER | BUCK | 3),
  MASTER_BUCK5  = (MASTER | BUCK | 4),
  MASTER_BUCK6  = (MASTER | BUCK | 5),
  MASTER_BUCK7  = (MASTER | BUCK | 6),
  MASTER_BUCK8  = (MASTER | BUCK | 7),
  MASTER_BUCK9  = (MASTER | BUCK | 8),
  MASTER_BUCK10 = (MASTER | BUCK | 9),

  MASTER_NLDO1 = (MASTER | NLDO | 0),
  MASTER_NLDO2 = (MASTER | NLDO | 1),
  MASTER_NLDO3 = (MASTER | NLDO | 2),
  MASTER_NLDO4 = (MASTER | NLDO | 3),
  MASTER_NLDO5 = (MASTER | NLDO | 4),

  MASTER_PLDO1 = (MASTER | PLDO | 0),
  MASTER_PLDO2 = (MASTER | PLDO | 1),
  MASTER_PLDO3 = (MASTER | PLDO | 2),
  MASTER_PLDO4 = (MASTER | PLDO | 3),
  MASTER_PLDO5 = (MASTER | PLDO | 4),
  MASTER_PLDO6 = (MASTER | PLDO | 5),
};

enum slaver_num {
  SLAVER_BUCK1  = (SLAVER | BUCK | 0),
  SLAVER_BUCK2  = (SLAVER | BUCK | 1),
  SLAVER_BUCK3  = (SLAVER | BUCK | 2),
  SLAVER_BUCK4  = (SLAVER | BUCK | 3),
  SLAVER_BUCK5  = (SLAVER | BUCK | 4),
  SLAVER_BUCK6  = (SLAVER | BUCK | 5),
  SLAVER_BUCK7  = (SLAVER | BUCK | 6),
  SLAVER_BUCK8  = (SLAVER | BUCK | 7),
  SLAVER_BUCK9  = (SLAVER | BUCK | 8),
  SLAVER_BUCK10 = (SLAVER | BUCK | 9),

  SLAVER_NLDO1 = (SLAVER | NLDO | 0),
  SLAVER_NLDO2 = (SLAVER | NLDO | 1),
  SLAVER_NLDO3 = (SLAVER | NLDO | 2),
  SLAVER_NLDO4 = (SLAVER | NLDO | 3),
  SLAVER_NLDO5 = (SLAVER | NLDO | 4),

  SLAVER_PLDO1 = (SLAVER | PLDO | 0),
  SLAVER_PLDO2 = (SLAVER | PLDO | 1),
  SLAVER_PLDO3 = (SLAVER | PLDO | 2),
  SLAVER_PLDO4 = (SLAVER | PLDO | 3),
  SLAVER_PLDO5 = (SLAVER | PLDO | 4),
  SLAVER_PLDO6 = (SLAVER | PLDO | 5),
};

#endif

struct SPI_HANDLE  gSPI;

static const struct rk8xx_reg_info  rk806_buck[] = {
  /* buck 1 */
  { 500000,  6250,  RK806_BUCK_ON_VSEL (1),  RK806_BUCK_SLP_VSEL (1),  RK806_BUCK_CONFIG (1),  RK806_BUCK_VSEL_MASK, 0x00, 0xa0, 3 },
  { 1500000, 25000, RK806_BUCK_ON_VSEL (1),  RK806_BUCK_SLP_VSEL (1),  RK806_BUCK_CONFIG (1),  RK806_BUCK_VSEL_MASK, 0xa1, 0xed, 3 },
  { 3400000, 0,     RK806_BUCK_ON_VSEL (1),  RK806_BUCK_SLP_VSEL (1),  RK806_BUCK_CONFIG (1),  RK806_BUCK_VSEL_MASK, 0xee, 0xff, 3 },
  /* buck 2 */
  { 500000,  6250,  RK806_BUCK_ON_VSEL (2),  RK806_BUCK_SLP_VSEL (2),  RK806_BUCK_CONFIG (2),  RK806_BUCK_VSEL_MASK, 0x00, 0xa0, 3 },
  { 1500000, 25000, RK806_BUCK_ON_VSEL (2),  RK806_BUCK_SLP_VSEL (2),  RK806_BUCK_CONFIG (2),  RK806_BUCK_VSEL_MASK, 0xa1, 0xed, 3 },
  { 3400000, 0,     RK806_BUCK_ON_VSEL (2),  RK806_BUCK_SLP_VSEL (2),  RK806_BUCK_CONFIG (2),  RK806_BUCK_VSEL_MASK, 0xee, 0xff, 3 },
  /* buck 3 */
  { 500000,  6250,  RK806_BUCK_ON_VSEL (3),  RK806_BUCK_SLP_VSEL (3),  RK806_BUCK_CONFIG (3),  RK806_BUCK_VSEL_MASK, 0x00, 0xa0, 3 },
  { 1500000, 25000, RK806_BUCK_ON_VSEL (3),  RK806_BUCK_SLP_VSEL (3),  RK806_BUCK_CONFIG (3),  RK806_BUCK_VSEL_MASK, 0xa1, 0xed, 3 },
  { 3400000, 0,     RK806_BUCK_ON_VSEL (3),  RK806_BUCK_SLP_VSEL (3),  RK806_BUCK_CONFIG (3),  RK806_BUCK_VSEL_MASK, 0xee, 0xff, 3 },
  /* buck 4 */
  { 500000,  6250,  RK806_BUCK_ON_VSEL (4),  RK806_BUCK_SLP_VSEL (4),  RK806_BUCK_CONFIG (4),  RK806_BUCK_VSEL_MASK, 0x00, 0xa0, 3 },
  { 1500000, 25000, RK806_BUCK_ON_VSEL (4),  RK806_BUCK_SLP_VSEL (4),  RK806_BUCK_CONFIG (4),  RK806_BUCK_VSEL_MASK, 0xa1, 0xed, 3 },
  { 3400000, 0,     RK806_BUCK_ON_VSEL (4),  RK806_BUCK_SLP_VSEL (4),  RK806_BUCK_CONFIG (4),  RK806_BUCK_VSEL_MASK, 0xee, 0xff, 3 },
  /* buck 5 */
  { 500000,  6250,  RK806_BUCK_ON_VSEL (5),  RK806_BUCK_SLP_VSEL (5),  RK806_BUCK_CONFIG (5),  RK806_BUCK_VSEL_MASK, 0x00, 0xa0, 3 },
  { 1500000, 25000, RK806_BUCK_ON_VSEL (5),  RK806_BUCK_SLP_VSEL (5),  RK806_BUCK_CONFIG (5),  RK806_BUCK_VSEL_MASK, 0xa1, 0xed, 3 },
  { 3400000, 0,     RK806_BUCK_ON_VSEL (5),  RK806_BUCK_SLP_VSEL (5),  RK806_BUCK_CONFIG (5),  RK806_BUCK_VSEL_MASK, 0xee, 0xff, 3 },
  /* buck 6 */
  { 500000,  6250,  RK806_BUCK_ON_VSEL (6),  RK806_BUCK_SLP_VSEL (6),  RK806_BUCK_CONFIG (6),  RK806_BUCK_VSEL_MASK, 0x00, 0xa0, 3 },
  { 1500000, 25000, RK806_BUCK_ON_VSEL (6),  RK806_BUCK_SLP_VSEL (6),  RK806_BUCK_CONFIG (6),  RK806_BUCK_VSEL_MASK, 0xa1, 0xed, 3 },
  { 3400000, 0,     RK806_BUCK_ON_VSEL (6),  RK806_BUCK_SLP_VSEL (6),  RK806_BUCK_CONFIG (6),  RK806_BUCK_VSEL_MASK, 0xee, 0xff, 3 },
  /* buck 7 */
  { 500000,  6250,  RK806_BUCK_ON_VSEL (7),  RK806_BUCK_SLP_VSEL (7),  RK806_BUCK_CONFIG (7),  RK806_BUCK_VSEL_MASK, 0x00, 0xa0, 3 },
  { 1500000, 25000, RK806_BUCK_ON_VSEL (7),  RK806_BUCK_SLP_VSEL (7),  RK806_BUCK_CONFIG (7),  RK806_BUCK_VSEL_MASK, 0xa1, 0xed, 3 },
  { 3400000, 0,     RK806_BUCK_ON_VSEL (7),  RK806_BUCK_SLP_VSEL (7),  RK806_BUCK_CONFIG (7),  RK806_BUCK_VSEL_MASK, 0xee, 0xff, 3 },
  /* buck 8 */
  { 500000,  6250,  RK806_BUCK_ON_VSEL (8),  RK806_BUCK_SLP_VSEL (8),  RK806_BUCK_CONFIG (8),  RK806_BUCK_VSEL_MASK, 0x00, 0xa0, 3 },
  { 1500000, 25000, RK806_BUCK_ON_VSEL (8),  RK806_BUCK_SLP_VSEL (8),  RK806_BUCK_CONFIG (8),  RK806_BUCK_VSEL_MASK, 0xa1, 0xed, 3 },
  { 3400000, 0,     RK806_BUCK_ON_VSEL (8),  RK806_BUCK_SLP_VSEL (8),  RK806_BUCK_CONFIG (8),  RK806_BUCK_VSEL_MASK, 0xee, 0xff, 3 },
  /* buck 9 */
  { 500000,  6250,  RK806_BUCK_ON_VSEL (9),  RK806_BUCK_SLP_VSEL (9),  RK806_BUCK_CONFIG (9),  RK806_BUCK_VSEL_MASK, 0x00, 0xa0, 3 },
  { 1500000, 25000, RK806_BUCK_ON_VSEL (9),  RK806_BUCK_SLP_VSEL (9),  RK806_BUCK_CONFIG (9),  RK806_BUCK_VSEL_MASK, 0xa1, 0xed, 3 },
  { 3400000, 0,     RK806_BUCK_ON_VSEL (9),  RK806_BUCK_SLP_VSEL (9),  RK806_BUCK_CONFIG (9),  RK806_BUCK_VSEL_MASK, 0xee, 0xff, 3 },
  /* buck 10 */
  { 500000,  6250,  RK806_BUCK_ON_VSEL (10), RK806_BUCK_SLP_VSEL (10), RK806_BUCK_CONFIG (10), RK806_BUCK_VSEL_MASK, 0x00, 0xa0, 3 },
  { 1500000, 25000, RK806_BUCK_ON_VSEL (10), RK806_BUCK_SLP_VSEL (10), RK806_BUCK_CONFIG (10), RK806_BUCK_VSEL_MASK, 0xa1, 0xed, 3 },
  { 3400000, 0,     RK806_BUCK_ON_VSEL (10), RK806_BUCK_SLP_VSEL (10), RK806_BUCK_CONFIG (10), RK806_BUCK_VSEL_MASK, 0xee, 0xff, 3 },
};

static const struct rk8xx_reg_info  rk806_nldo[] = {
  /* nldo1 */
  { 500000,  12500, RK806_NLDO_ON_VSEL (1), RK806_NLDO_SLP_VSEL (1), NA, RK806_NLDO_VSEL_MASK, 0x00, },
  { 3400000, 0,     RK806_NLDO_ON_VSEL (1), RK806_NLDO_SLP_VSEL (1), NA, RK806_NLDO_VSEL_MASK, 0xE8, },
  /* nldo2 */
  { 500000,  12500, RK806_NLDO_ON_VSEL (2), RK806_NLDO_SLP_VSEL (2), NA, RK806_NLDO_VSEL_MASK, 0x00, },
  { 3400000, 0,     RK806_NLDO_ON_VSEL (2), RK806_NLDO_SLP_VSEL (2), NA, RK806_NLDO_VSEL_MASK, 0xE8, },
  /* nldo3 */
  { 500000,  12500, RK806_NLDO_ON_VSEL (3), RK806_NLDO_SLP_VSEL (3), NA, RK806_NLDO_VSEL_MASK, 0x00, },
  { 3400000, 0,     RK806_NLDO_ON_VSEL (3), RK806_NLDO_SLP_VSEL (3), NA, RK806_NLDO_VSEL_MASK, 0xE8, },
  /* nldo4 */
  { 500000,  12500, RK806_NLDO_ON_VSEL (4), RK806_NLDO_SLP_VSEL (4), NA, RK806_NLDO_VSEL_MASK, 0x00, },
  { 3400000, 0,     RK806_NLDO_ON_VSEL (4), RK806_NLDO_SLP_VSEL (4), NA, RK806_NLDO_VSEL_MASK, 0xE8, },
  /* nldo5 */
  { 500000,  12500, RK806_NLDO_ON_VSEL (5), RK806_NLDO_SLP_VSEL (5), NA, RK806_NLDO_VSEL_MASK, 0x00, },
  { 3400000, 0,     RK806_NLDO_ON_VSEL (5), RK806_NLDO_SLP_VSEL (5), NA, RK806_NLDO_VSEL_MASK, 0xE8, },
};

static const struct rk8xx_reg_info  rk806_pldo[] = {
  /* pldo1 */
  { 500000,  12500, RK806_PLDO_ON_VSEL (1), RK806_PLDO_SLP_VSEL (1), NA, RK806_PLDO_VSEL_MASK, 0x00, },
  { 3400000, 0,     RK806_PLDO_ON_VSEL (1), RK806_PLDO_SLP_VSEL (1), NA, RK806_PLDO_VSEL_MASK, 0xE8, },
  /* pldo2 */
  { 500000,  12500, RK806_PLDO_ON_VSEL (2), RK806_PLDO_SLP_VSEL (2), NA, RK806_PLDO_VSEL_MASK, 0x00, },
  { 3400000, 0,     RK806_PLDO_ON_VSEL (2), RK806_PLDO_SLP_VSEL (2), NA, RK806_PLDO_VSEL_MASK, 0xE8, },
  /* pldo3 */
  { 500000,  12500, RK806_PLDO_ON_VSEL (3), RK806_PLDO_SLP_VSEL (3), NA, RK806_PLDO_VSEL_MASK, 0x00, },
  { 3400000, 0,     RK806_PLDO_ON_VSEL (3), RK806_PLDO_SLP_VSEL (3), NA, RK806_PLDO_VSEL_MASK, 0xE8, },
  /* pldo4 */
  { 500000,  12500, RK806_PLDO_ON_VSEL (4), RK806_PLDO_SLP_VSEL (4), NA, RK806_PLDO_VSEL_MASK, 0x00, },
  { 3400000, 0,     RK806_PLDO_ON_VSEL (4), RK806_PLDO_SLP_VSEL (4), NA, RK806_PLDO_VSEL_MASK, 0xE8, },
  /* pldo5 */
  { 500000,  12500, RK806_PLDO_ON_VSEL (5), RK806_PLDO_SLP_VSEL (5), NA, RK806_PLDO_VSEL_MASK, 0x00, },
  { 3400000, 0,     RK806_PLDO_ON_VSEL (5), RK806_PLDO_SLP_VSEL (5), NA, RK806_PLDO_VSEL_MASK, 0xE8, },
  /* pldo6 */
  { 500000,  12500, RK806_PLDO_ON_VSEL (6), RK806_PLDO_SLP_VSEL (6), NA, RK806_PLDO_VSEL_MASK, 0x00, },
  { 3400000, 0,     RK806_PLDO_ON_VSEL (6), RK806_PLDO_SLP_VSEL (6), NA, RK806_PLDO_VSEL_MASK, 0xE8, },
};

static const struct rk806_pin_config  rk806_gpio_cfgs[] = {
  {
    .fun_reg = RK806_SLEEP_CONFIG0,
    .fun_msk = RK806_PWRCTRL1_FUN,
    .reg     = RK806_SLEEP_GPIO,
    .val_msk = RK806_PWRCTRL1_DATA,
    .dir_msk = RK806_PWRCTRL1_DR,
  },
  {
    .fun_reg = RK806_SLEEP_CONFIG0,
    .fun_msk = RK806_PWRCTRL2_FUN,
    .reg     = RK806_SLEEP_GPIO,
    .val_msk = RK806_PWRCTRL2_DATA,
    .dir_msk = RK806_PWRCTRL2_DR,
  },
  {
    .fun_reg = RK806_SLEEP_CONFIG1,
    .fun_msk = RK806_PWRCTRL3_FUN,
    .reg     = RK806_SLEEP_GPIO,
    .val_msk = RK806_PWRCTRL3_DATA,
    .dir_msk = RK806_PWRCTRL3_DR,
  }
};

#if 0
static void
io_mem_show (
  const char     *label,
  unsigned long  base,
  unsigned int   start,
  unsigned int   end
  )
{
  unsigned int  val, offset = start, nr = 0;

  if (label) {
    DEBUG ((DEBUG_ERROR, "%a:\n", label));
  }

  DEBUG ((DEBUG_ERROR, "%08lx:  ", base + offset));
  for (offset = start; offset <= end; offset += 0x04) {
    if (nr >= 4) {
      DEBUG ((DEBUG_ERROR, "\n%08lx:  ", base + offset));
      nr = 0;
    }

    val = MmioRead32 (base + offset);
    DEBUG ((DEBUG_ERROR, "%08lx ", val));
    nr++;
  }

  DEBUG ((DEBUG_ERROR, "\n"));
}

#endif

static RETURN_STATUS
_spi_read (
  INT32   cs_id,
  UINT32  reg,
  UINT8   *buffer,
  INT32   len
  )
{
  UINT8          txbuf[8];
  RETURN_STATUS  status;

  txbuf[0] = RK806_CMD_READ;
  txbuf[1] = reg;
  txbuf[2] = RK806_REG_H;

  SPI_SetCS (&gSPI, cs_id, 1);
  status = SPI_Configure (&gSPI, txbuf, NULL, 3);
  status = SPI_PioTransfer (&gSPI);
  SPI_Stop (&gSPI);
  status = SPI_Configure (&gSPI, NULL, buffer, 1);
  status = SPI_PioTransfer (&gSPI);
  SPI_Stop (&gSPI);
  SPI_SetCS (&gSPI, cs_id, 0);

  return status;
}

static RETURN_STATUS
_spi_write (
  INT32        cs_id,
  UINT32       reg,
  const UINT8  *buffer,
  INT32        len
  )
{
  UINT8          txbuf[4];
  RETURN_STATUS  status;

  txbuf[0] = RK806_CMD_WRITE;
  txbuf[1] = reg;
  txbuf[2] = RK806_REG_H;
  txbuf[3] = *buffer;
  SPI_SetCS (&gSPI, cs_id, 1);
  status = SPI_Configure (&gSPI, txbuf, NULL, 4);
  status = SPI_PioTransfer (&gSPI);

  SPI_Stop (&gSPI);
  SPI_SetCS (&gSPI, cs_id, 0);

  return status;
}

static RETURN_STATUS
pmic_reg_read (
  INT32   cs_id,
  UINT32  reg,
  UINT8   *buffer,
  INT32   len
  )
{
  RETURN_STATUS  ret;

  ret = _spi_read (cs_id, reg, buffer, len);
  if (ret) {
    DEBUG ((
      DEBUG_ERROR,
      "cs_id %d rk806 read reg(0x%x) error: %d buffer = %x\n",
      cs_id,
      reg,
      ret,
      buffer[0]
      ));
  }

  return ret;
}

static INT32
pmic_reg_write (
  INT32        cs_id,
  UINT32       reg,
  const UINT8  *buffer,
  INT32        len
  )
{
  RETURN_STATUS  ret;

  ret = _spi_write (cs_id, reg, buffer, len);
  if (ret) {
    DEBUG ((
      DEBUG_ERROR,
      "cs_id %d rk806 write reg(0x%x) error: %d %x\n",
      cs_id,
      reg,
      ret,
      buffer[0]
      ));
  }

  return ret;
}

static RETURN_STATUS
pmic_clrsetbits (
  INT32   cs_id,
  UINT32  reg,
  UINT32  clr,
  UINT32  set
  )
{
  UINT8          byte;
  RETURN_STATUS  ret;

  ret = pmic_reg_read (cs_id, reg, &byte, 0x01);
  if (ret) {
    return ret;
  }

  byte = (byte & ~clr) | set;

  pmic_reg_write (cs_id, reg, &byte, 1);
  ret = pmic_reg_read (cs_id, reg, &byte, 0x01);
  return ret;
}

static const struct rk8xx_reg_info *
get_buck_reg (
  INT32  num,
  INT32  uvolt
  )
{
  if (uvolt < 1500000) {
    return &rk806_buck[num * 3 + 0];
  } else if (uvolt < 3400000) {
    return &rk806_buck[num * 3 + 1];
  } else {
    return &rk806_buck[num * 3 + 2];
  }
}

static const struct rk8xx_reg_info *
get_nldo_reg (
  INT32  num,
  INT32  uvolt
  )
{
  if (uvolt < 3400000) {
    return &rk806_nldo[num * 2];
  } else {
    return &rk806_nldo[num * 2 + 1];
  }
}

static const struct rk8xx_reg_info *
get_pldo_reg (
  INT32  num,
  INT32  uvolt
  )
{
  if (uvolt < 3400000) {
    return &rk806_pldo[num * 2];
  } else {
    return &rk806_pldo[num * 2 + 1];
  }
}

static RETURN_STATUS
buck_set_voltage (
  INT32  reg_id,
  INT32  uvolt
  )
{
  INT32                        buck  = reg_id & 0x0f;
  const struct rk8xx_reg_info  *info = get_buck_reg (buck, uvolt);
  INT32                        mask  = info->vsel_mask;
  INT32                        cs_id = (reg_id & 0xf00) >> 8;
  INT32                        val;

  if (info->vsel_reg == NA) {
    return RETURN_INVALID_PARAMETER;
  }

  if (info->step_uv == 0) {
    /* Fixed voltage */
    val = info->min_sel;
  } else {
    val = ((uvolt - info->min_uv) / info->step_uv) + info->min_sel;
  }

  DEBUG ((
    DEBUG_INFO,
    "%a: volt=%d, buck=%d, reg=0x%x, mask=0x%x, val=0x%x\n",
    __func__,
    uvolt,
    buck + 1,
    info->vsel_reg,
    mask,
    val
    ));

  return pmic_clrsetbits (cs_id, info->vsel_reg, mask, val);
}

static RETURN_STATUS
nldo_set_voltage (
  INT32  reg_id,
  INT32  uvolt
  )
{
  INT32                        ldo   = reg_id & 0x0f;
  const struct rk8xx_reg_info  *info = get_nldo_reg (ldo, uvolt);
  INT32                        mask  = info->vsel_mask;
  INT32                        cs_id = (reg_id & 0xf00) >> 8;
  INT32                        val;

  if (info->vsel_reg == NA) {
    return RETURN_INVALID_PARAMETER;
  }

  if (info->step_uv == 0) {
    val = info->min_sel;
  } else {
    val = ((uvolt - info->min_uv) / info->step_uv) + info->min_sel;
  }

  DEBUG ((
    DEBUG_INFO,
    "%a: volt=%d, ldo=%d, reg=0x%x, mask=0x%x, val=0x%x\n",
    __func__,
    uvolt,
    ldo + 1,
    info->vsel_reg,
    mask,
    val
    ));

  return pmic_clrsetbits (cs_id, info->vsel_reg, mask, val);
}

static RETURN_STATUS
pldo_set_voltage (
  INT32  reg_id,
  INT32  uvolt
  )
{
  INT32                        ldo   = reg_id & 0x0f;
  const struct rk8xx_reg_info  *info = get_pldo_reg (ldo, uvolt);
  INT32                        mask  = info->vsel_mask;
  INT32                        cs_id = (reg_id & 0xf00) >> 8;
  INT32                        val;

  if (info->vsel_reg == NA) {
    return RETURN_INVALID_PARAMETER;
  }

  if (info->step_uv == 0) {
    val = info->min_sel;
  } else {
    val = ((uvolt - info->min_uv) / info->step_uv) + info->min_sel;
  }

  DEBUG ((
    DEBUG_INFO,
    "%s: volt=%d, ldo=%d, reg=0x%x, mask=0x%x, val=0x%x\n",
    __func__,
    uvolt,
    ldo + 1,
    info->vsel_reg,
    mask,
    val
    ));

  return pmic_clrsetbits (cs_id, info->vsel_reg, mask, val);
}

static RETURN_STATUS
buck_set_enable (
  INT32    reg_id,
  BOOLEAN  enable
  )
{
  INT32          cs_id = (reg_id & 0xf00) >> 8;
  INT32          buck = reg_id & 0x0f;
  UINT8          value, en_reg;
  RETURN_STATUS  ret;

  en_reg = RK806_POWER_EN (buck / 4);
  if (enable) {
    value = ((1 << buck % 4) | (1 << (buck % 4 + 4)));
  } else {
    value = ((0 << buck % 4) | (1 << (buck % 4 + 4)));
  }

  ret = pmic_reg_write (cs_id, en_reg, &value, 1);

  return ret;
}

static RETURN_STATUS
nldo_set_enable (
  INT32    reg_id,
  BOOLEAN  enable
  )
{
  INT32          cs_id = (reg_id & 0xf00) >> 8;
  INT32          ldo = reg_id & 0x0f;
  UINT8          value, en_reg;
  RETURN_STATUS  ret;

  if (ldo < 4) {
    en_reg = RK806_NLDO_EN (0);
    if (enable) {
      value = ((1 << ldo % 4) | (1 << (ldo % 4 + 4)));
    } else {
      value = ((0 << ldo % 4) | (1 << (ldo % 4 + 4)));
    }

    ret =  pmic_reg_write (cs_id, en_reg, &value, 1);
  } else {
    en_reg = RK806_NLDO_EN (2);
    if (enable) {
      value = 0x44;
    } else {
      value = 0x40;
    }

    ret = pmic_reg_write (cs_id, en_reg, &value, 1);
  }

  return ret;
}

static RETURN_STATUS
pldo_set_enable (
  INT32    reg_id,
  BOOLEAN  enable
  )
{
  INT32          cs_id = (reg_id & 0xf00) >> 8;
  INT32          pldo = reg_id & 0x0f;
  UINT8          value, en_reg;
  RETURN_STATUS  ret;

  if (pldo < 3) {
    en_reg = RK806_PLDO_EN (0);
    if (enable) {
      value = RK806_PLDO0_2_SET (pldo);
    } else {
      value = RK806_PLDO0_2_CLR (pldo);
    }

    ret =  pmic_reg_write (cs_id, en_reg, &value, 1);
  } else if (pldo == 3) {
    en_reg = RK806_PLDO_EN (1);
    if (enable) {
      value = ((1 << 0) | (1 << 4));
    } else {
      value = (1 << 4);
    }

    ret =  pmic_reg_write (cs_id, en_reg, &value, 1);
  } else if (pldo == 4) {
    en_reg = RK806_PLDO_EN (1);
    if (enable) {
      value = ((1 << 1) | (1 << 5));
    } else {
      value = ((0 << 1) | (1 << 5));
    }

    ret =  pmic_reg_write (cs_id, en_reg, &value, 1);
  } else if (pldo == 5) {
    en_reg = RK806_PLDO_EN (0);
    if (enable) {
      value = ((1 << 0) | (1 << 4));
    } else {
      value = ((0 << 0) | (1 << 4));
    }

    ret =  pmic_reg_write (cs_id, en_reg, &value, 1);
  }

  return ret;
}

void
RK806RegulatorInit (
  struct regulator_init_data  init_data
  )
{
  INT32  reg_id;
  INT32  init_voltage;

  init_voltage = init_data.init_voltage_mv;
  reg_id       = init_data.reg_id;

  if ((reg_id & 0xf0) == BUCK) {
    buck_set_voltage (reg_id, init_voltage);
    buck_set_enable (reg_id, 1);
  } else if ((reg_id & 0xf0) == NLDO) {
    nldo_set_voltage (reg_id, init_voltage);
    nldo_set_enable (reg_id, 1);
  }

  if ((reg_id & 0xf0) == PLDO) {
    pldo_set_voltage (reg_id, init_voltage);
    pldo_set_enable (reg_id, 1);
  }
}

static
RETURN_STATUS
SpiCongig (
  struct SPI_HANDLE  *pSPI
  )
{
  struct SPI_CONFIG  *pSPIConfig = &pSPI->config;

  DEBUG ((DEBUG_INFO, "%a(%u): 0: %x\n", "SpiCongig", __LINE__, 0));

  /* Data width */
  pSPIConfig->nBytes = CR0_DATA_FRAME_SIZE_8BIT;

  /* CPOL */
  // pSPIConfig->clkPolarity = CR0_POLARITY_HIGH;
  pSPIConfig->clkPolarity = CR0_POLARITY_LOW;

  /* CPHA */
  // pSPIConfig->clkPhase = CR0_PHASE_2EDGE;
  pSPIConfig->clkPhase = CR0_PHASE_1EDGE;

  /* MSB or LSB */
  pSPIConfig->firstBit = CR0_FIRSTBIT_MSB;
  // pSPIConfig->firstBit = CR0_FIRSTBIT_LSB;

  /* Master or Slave */
  // pSPIConfig->opMode = CR0_OPM_SLAVE;
  pSPIConfig->opMode = CR0_OPM_MASTER;

  /* CSM cycles */
  pSPIConfig->csm = 0;

  pSPI->maxFreq     = 24000000;
  pSPIConfig->speed = 2000000;

  if (pSPI->config.speed > HAL_SPI_MASTER_MAX_SCLK_OUT) {
    pSPI->config.speed = HAL_SPI_MASTER_MAX_SCLK_OUT;
  }

  return RETURN_SUCCESS;
}

RETURN_STATUS
RK806Init (
  void
  )
{
  UINT32  base = FixedPcdGet32 (SpiRK806BaseAddr);

  Rk806SpiIomux ();

  DEBUG ((DEBUG_INIT, "%a(%u): base: %x\n", "RK806Init", __LINE__, base));

  SPI_Init (&gSPI, base);
  SpiCongig (&gSPI);

  return RETURN_SUCCESS;
}

RETURN_STATUS
RK806PinSetFunction (
  IN UINT8  RegId,
  IN UINT8  Pin,
  IN UINT8  Function
  )
{
  if ((Pin < 1) || (Pin > 3) || (Function > 5)) {
    ASSERT (FALSE);
    return RETURN_INVALID_PARAMETER;
  }

  const struct rk806_pin_config  *conf = &rk806_gpio_cfgs[Pin - 1];
  UINT8                          mask  = conf->fun_msk;
  UINT8                          cs_id = (RegId & 0xf00) >> 8;
  UINT8                          val;

  DEBUG ((
    DEBUG_INFO,
    "%a: RegId=0x%x, Pin=%u, Function=%u\n",
    __func__,
    RegId,
    Pin,
    Function
    ));

  val = Function << LowBitSet32 (mask);

  return pmic_clrsetbits (cs_id, conf->fun_reg, mask, val);
}
